Implement icon theme caching. (#154034, Martijn Vernooij, caching schema
authorMatthias Clasen <mclasen@redhat.com>
Tue, 19 Oct 2004 18:45:41 +0000 (18:45 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Tue, 19 Oct 2004 18:45:41 +0000 (18:45 +0000)
2004-10-19  Matthias Clasen  <mclasen@redhat.com>

Implement icon theme caching.  (#154034, Martijn Vernooij,
caching schema proposed by Owen Taylor, initial implementation
by Anders Carlsson)

* gtk/gtkdebug.h:
* gtk/gtkmain.c: Add a "icontheme" debug flag.

* gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c
(gtk_private_h_sources): Add gtkiconcache.h
(bin_PROGRAMS): Add gtk-update-icon-cache

* gtk/gtkicontheme.c: Use icon caches if they are available.
Currently, GTK+ uses the cache to get information about the
available sizes, image file formats and .icon files. The
actual image data, and the .icon file contents are not
cached yet.

* gtk/updateiconcache.c: A cmdline utility for generating
icon cache files.

* gtk/gtkiconcache.h:
* gtk/gtkiconcache.c: The glue code to mmap an icon cache
file and manage the information it contains.

16 files changed:
ChangeLog
ChangeLog.pre-2-10
ChangeLog.pre-2-6
ChangeLog.pre-2-8
docs/reference/ChangeLog
docs/reference/gtk/Makefile.am
docs/reference/gtk/gtk-docs.sgml
docs/reference/gtk/gtk-update-icon-cache.1 [new file with mode: 0644]
docs/reference/gtk/gtk-update-icon-cache.xml [new file with mode: 0644]
gtk/Makefile.am
gtk/gtkdebug.h
gtk/gtkiconcache.c [new file with mode: 0644]
gtk/gtkiconcache.h [new file with mode: 0644]
gtk/gtkicontheme.c
gtk/gtkmain.c
gtk/updateiconcache.c [new file with mode: 0644]

index c1a7c9d89aacb1f6426d629b80b30de1aa7e8eac..e04e49c0524d6255aaa4d58553388c5c83a74450 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2004-10-19  Matthias Clasen  <mclasen@redhat.com>
+
+       Implement icon theme caching.  (#154034, Martijn Vernooij,
+       caching schema proposed by Owen Taylor, initial implementation
+       by Anders Carlsson)
+       
+       * gtk/gtkdebug.h: 
+       * gtk/gtkmain.c: Add a "icontheme" debug flag.
+
+       * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c
+       (gtk_private_h_sources): Add gtkiconcache.h
+       (bin_PROGRAMS): Add gtk-update-icon-cache
+
+       * gtk/gtkicontheme.c: Use icon caches if they are available.
+       Currently, GTK+ uses the cache to get information about the
+       available sizes, image file formats and .icon files. The
+       actual image data, and the .icon file contents are not 
+       cached yet.
+
+       * gtk/updateiconcache.c: A cmdline utility for generating
+       icon cache files.
+
+       * gtk/gtkiconcache.h: 
+       * gtk/gtkiconcache.c: The glue code to mmap an icon cache
+       file and manage the information it contains.
+
 2004-10-19  Matthias Clasen  <mclasen@redhat.com>
 
        * tests/testicontheme.c: Set the locale, tidy up output.
index c1a7c9d89aacb1f6426d629b80b30de1aa7e8eac..e04e49c0524d6255aaa4d58553388c5c83a74450 100644 (file)
@@ -1,3 +1,29 @@
+2004-10-19  Matthias Clasen  <mclasen@redhat.com>
+
+       Implement icon theme caching.  (#154034, Martijn Vernooij,
+       caching schema proposed by Owen Taylor, initial implementation
+       by Anders Carlsson)
+       
+       * gtk/gtkdebug.h: 
+       * gtk/gtkmain.c: Add a "icontheme" debug flag.
+
+       * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c
+       (gtk_private_h_sources): Add gtkiconcache.h
+       (bin_PROGRAMS): Add gtk-update-icon-cache
+
+       * gtk/gtkicontheme.c: Use icon caches if they are available.
+       Currently, GTK+ uses the cache to get information about the
+       available sizes, image file formats and .icon files. The
+       actual image data, and the .icon file contents are not 
+       cached yet.
+
+       * gtk/updateiconcache.c: A cmdline utility for generating
+       icon cache files.
+
+       * gtk/gtkiconcache.h: 
+       * gtk/gtkiconcache.c: The glue code to mmap an icon cache
+       file and manage the information it contains.
+
 2004-10-19  Matthias Clasen  <mclasen@redhat.com>
 
        * tests/testicontheme.c: Set the locale, tidy up output.
index c1a7c9d89aacb1f6426d629b80b30de1aa7e8eac..e04e49c0524d6255aaa4d58553388c5c83a74450 100644 (file)
@@ -1,3 +1,29 @@
+2004-10-19  Matthias Clasen  <mclasen@redhat.com>
+
+       Implement icon theme caching.  (#154034, Martijn Vernooij,
+       caching schema proposed by Owen Taylor, initial implementation
+       by Anders Carlsson)
+       
+       * gtk/gtkdebug.h: 
+       * gtk/gtkmain.c: Add a "icontheme" debug flag.
+
+       * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c
+       (gtk_private_h_sources): Add gtkiconcache.h
+       (bin_PROGRAMS): Add gtk-update-icon-cache
+
+       * gtk/gtkicontheme.c: Use icon caches if they are available.
+       Currently, GTK+ uses the cache to get information about the
+       available sizes, image file formats and .icon files. The
+       actual image data, and the .icon file contents are not 
+       cached yet.
+
+       * gtk/updateiconcache.c: A cmdline utility for generating
+       icon cache files.
+
+       * gtk/gtkiconcache.h: 
+       * gtk/gtkiconcache.c: The glue code to mmap an icon cache
+       file and manage the information it contains.
+
 2004-10-19  Matthias Clasen  <mclasen@redhat.com>
 
        * tests/testicontheme.c: Set the locale, tidy up output.
index c1a7c9d89aacb1f6426d629b80b30de1aa7e8eac..e04e49c0524d6255aaa4d58553388c5c83a74450 100644 (file)
@@ -1,3 +1,29 @@
+2004-10-19  Matthias Clasen  <mclasen@redhat.com>
+
+       Implement icon theme caching.  (#154034, Martijn Vernooij,
+       caching schema proposed by Owen Taylor, initial implementation
+       by Anders Carlsson)
+       
+       * gtk/gtkdebug.h: 
+       * gtk/gtkmain.c: Add a "icontheme" debug flag.
+
+       * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c
+       (gtk_private_h_sources): Add gtkiconcache.h
+       (bin_PROGRAMS): Add gtk-update-icon-cache
+
+       * gtk/gtkicontheme.c: Use icon caches if they are available.
+       Currently, GTK+ uses the cache to get information about the
+       available sizes, image file formats and .icon files. The
+       actual image data, and the .icon file contents are not 
+       cached yet.
+
+       * gtk/updateiconcache.c: A cmdline utility for generating
+       icon cache files.
+
+       * gtk/gtkiconcache.h: 
+       * gtk/gtkiconcache.c: The glue code to mmap an icon cache
+       file and manage the information it contains.
+
 2004-10-19  Matthias Clasen  <mclasen@redhat.com>
 
        * tests/testicontheme.c: Set the locale, tidy up output.
index f7f9929bfbdebe16ed68885176cd09faa2b68f64..b5b11b10402ecef8c5e346a9e2617f9c417b637e 100644 (file)
@@ -1,3 +1,11 @@
+2004-10-19  Matthias Clasen  <mclasen@redhat.com>
+
+       * gtk/gtk-update-icon-cache.xml: A man page for gtk-update-icon-cache.
+
+       * gtk/gtk-docs.sgml: Add it here        
+
+       * gtk/Makefile.am (man_MANS): ...and here.
+
 2004-10-16  Matthias Clasen  <mclasen@redhat.com>
 
        * gtk/glossary.xml: Additions.
index 52b66bf38a68cba58c0ae6ca2cc33d59518cedd0..57ab11c30c69dd98e985673bfedc8ba289dd5900 100644 (file)
@@ -104,6 +104,7 @@ content_files =                                     \
        windows.sgml                            \
        x11.sgml                                \
        gtk-query-immodules-2.0.xml             \
+       gtk-update-icon-cache.xml               \
        visual_index.xml
 
 # Images to copy into HTML directory
@@ -240,7 +241,7 @@ EXTRA_DIST += version.xml.in
 
 ########################################################################
 
-man_MANS = gtk-query-immodules-2.0.1 
+man_MANS = gtk-query-immodules-2.0.1 gtk-update-icon-cache.1 
 
 if ENABLE_MAN
 
index 2ced4af893d697882db221a12d5b67da9bf2f0a7..462afea8a5043049348b1cdca8cf271f2970aa24 100644 (file)
 <!ENTITY gtk-migrating-GtkComboBox SYSTEM "migrating-GtkComboBox.sgml">
 <!ENTITY version SYSTEM "version.xml">
 <!ENTITY gtk-query-immodules SYSTEM "gtk-query-immodules-2.0.xml">
+<!ENTITY gtk-update-icon-cache SYSTEM "gtk-update-icon-cache.xml">
 <!ENTITY gtk-glossary SYSTEM "glossary.xml">
 ]>
 <book id="index">
@@ -576,6 +577,7 @@ that is, GUI components such as <link linkend="GtkButton">GtkButton</link> or
     <title>GTK+ Tools</title>
 
      &gtk-query-immodules;
+     &gtk-update-icon-cache;
   </part>
 
   &gtk-glossary;
diff --git a/docs/reference/gtk/gtk-update-icon-cache.1 b/docs/reference/gtk/gtk-update-icon-cache.1
new file mode 100644 (file)
index 0000000..162a97f
--- /dev/null
@@ -0,0 +1,49 @@
+.\"Generated by db2man.xsl. Don't modify this, modify the source.
+.de Sh \" Subsection
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Ip \" List item
+.br
+.ie \\n(.$>=3 .ne \\$3
+.el .ne 3
+.IP "\\$1" \\$2
+..
+.TH "GTK-UPDATE-ICON-CA" 1 "" "" ""
+.SH NAME
+gtk-update-icon-cache \- Icon theme caching utility
+.SH "SYNOPSIS"
+.ad l
+.hy 0
+.HP 22
+\fBgtk\-update\-icon\-cache\fR [\-\-force] {iconpath}
+.ad
+.hy
+
+.SH "DESCRIPTION"
+
+.PP
+ \fBgtk\-update\-icon\-cache\fR creates mmap()able cache files for icon themes\&.
+
+.PP
+If expects to be given the path to a icon theme directory, e\&.g\&. \fI/usr/share/icons/hicolor\fR, and writes a \fIicon\-theme\&.cache\fR containing cached information about the icons in the directory tree below the given directory\&.
+
+.PP
+GTK+ can use the cache files created by \fBgtk\-update\-icon\-cache\fR to avoid a lot of system call and disk seek overhead when the application starts\&. Since the format of the cache files allows them to be mmap()ed shared between multiple applications, the overall memory consumption is reduced as well\&.
+
+.PP
+If called with the [\-\-force] argument, \fBgtk\-update\-icon\-cache\fR will overwrite an existing cache file even if it appears to be uptodate\&.
+
+.SH "BUGS"
+
+.PP
+None known yet\&.
+
diff --git a/docs/reference/gtk/gtk-update-icon-cache.xml b/docs/reference/gtk/gtk-update-icon-cache.xml
new file mode 100644 (file)
index 0000000..e7baad8
--- /dev/null
@@ -0,0 +1,53 @@
+<refentry id="gtk-update-icon-cache">
+
+<refmeta>
+<refentrytitle>gtk-update-icon-cache</refentrytitle>
+<manvolnum>1</manvolnum>
+</refmeta>
+
+<refnamediv>
+<refname>gtk-update-icon-cache</refname>
+<refpurpose>Icon theme caching utility</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+<cmdsynopsis>
+<command>gtk-update-icon-cache</command>
+<arg choice="opt">--force</arg>
+<arg choice="req">iconpath</arg>
+</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1><title>Description</title>
+<para>
+<command>gtk-update-icon-cache</command> creates mmap()able cache files for
+icon themes. 
+</para>
+<para>
+If expects to be given the path to a icon theme directory, e.g. 
+<filename>/usr/share/icons/hicolor</filename>, and writes a 
+<filename>icon-theme.cache</filename> containing cached information
+about the icons in the directory tree below the given directory.
+</para>
+<para>
+GTK+ can use the cache files created by <command>gtk-update-icon-cache</command>
+to avoid a lot of system call and disk seek overhead when the application starts.
+Since the format of the cache files allows them to be mmap()ed shared between
+multiple applications, the overall memory consumption is reduced as well.
+</para>
+<para>
+If called with the --force argument, 
+<command>gtk-update-icon-cache</command> will overwrite an existing cache
+file even if it appears to be uptodate.
+</para>
+</refsect1>
+
+<refsect1><title>Bugs</title>
+<para>
+None known yet.
+</para>
+</refsect1>
+
+</refentry>
+
+
index 0a6987d6908571a292e35705a7ef1ffabdee0499..8dcbc343f8aae97d150e456681bc4f6a44230547 100644 (file)
@@ -297,6 +297,7 @@ gtk_private_h_sources =             \
        gtkfilechooserutils.h   \
        gtkfilesystemunix.h     \
        gtkfilesystemmodel.h    \
+       gtkiconcache.h          \
        gtkpathbar.h            \
        gtkrbtree.h             \
        gtksequence.h           \
@@ -393,6 +394,7 @@ gtk_c_sources =                 \
        gtkhsv.c                \
        gtkhsv.h                \
        gtkiconfactory.c        \
+       gtkiconcache.c          \
        gtkicontheme.c          \
        gtkiconthemeparser.c    \
        gtkiconthemeparser.h    \
@@ -701,13 +703,19 @@ LDADDS =                                                          \
 #
 # Installed tools
 #
-bin_PROGRAMS = gtk-query-immodules-2.0
+bin_PROGRAMS = gtk-query-immodules-2.0 gtk-update-icon-cache
 
 gtk_query_immodules_2_0_DEPENDENCIES = $(DEPS)
 gtk_query_immodules_2_0_LDADD = $(LDADDS)
 
 gtk_query_immodules_2_0_SOURCES = queryimmodules.c
 
+
+gtk_update_icon_cache_DEPENDENCIES = $(DEPS)
+gtk_update_icon_cache_LDADD = $(LDADDS)
+
+gtk_update_icon_cache_SOURCES = updateiconcache.c
+
 .PHONY: files test test-debug
 
 files:
index ff1c0e74a28211656d97282185e45d3dadadd703..2edfb415759569971c45ea7751e1ecd3c4a8df0a 100644 (file)
@@ -40,7 +40,8 @@ typedef enum {
   GTK_DEBUG_KEYBINDINGS = 1 << 5,
   GTK_DEBUG_MULTIHEAD   = 1 << 6,
   GTK_DEBUG_MODULES     = 1 << 7,
-  GTK_DEBUG_GEOMETRY    = 1 << 8
+  GTK_DEBUG_GEOMETRY    = 1 << 8,
+  GTK_DEBUG_ICONTHEME   = 1 << 9
 } GtkDebugFlag;
 
 #ifdef G_ENABLE_DEBUG
diff --git a/gtk/gtkiconcache.c b/gtk/gtkiconcache.c
new file mode 100644 (file)
index 0000000..766dfa0
--- /dev/null
@@ -0,0 +1,280 @@
+/* gtkiconcache.c
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gtkdebug.h"
+#include "gtkiconcache.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <string.h>
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 0
+
+#define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
+#define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
+
+struct _GtkIconCache {
+  gint ref_count;
+
+  gsize size;
+  gchar *buffer;
+};
+
+GtkIconCache *
+_gtk_icon_cache_ref (GtkIconCache *cache)
+{
+  cache->ref_count ++;
+
+  return cache;
+}
+
+void
+_gtk_icon_cache_unref (GtkIconCache *cache)
+{
+  cache->ref_count --;
+
+  if (cache->ref_count == 0)
+    {
+      GTK_NOTE (ICONTHEME, 
+               g_print ("unmapping icon cache\n"));
+
+      munmap (cache->buffer, cache->size);
+      g_free (cache);
+    }
+}
+
+GtkIconCache *
+_gtk_icon_cache_new_for_path (const gchar *path)
+{
+  gchar *cache_filename;
+  gint fd;
+  struct stat st;
+  struct stat path_st;
+  gchar *buffer;
+  GtkIconCache *cache = NULL;
+
+  if (g_getenv ("GTK_NO_ICON_CACHE"))
+    return NULL;
+
+  /* Check if we have a cache file */
+  cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
+
+  GTK_NOTE (ICONTHEME, 
+           g_print ("look for cache in %s\n", path));
+
+  if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
+    {
+      g_free (cache_filename);
+      return NULL;
+    };
+
+  /* Open the file and mmap it */
+  fd = open (cache_filename, O_RDONLY);
+
+  g_free (cache_filename);
+  
+  if (fd < 0)
+    return NULL;
+  
+  if (fstat (fd, &st) < 0)
+    goto done;
+
+  if (stat (path, &path_st) < 0)
+    goto done;
+
+  /* Verify cache is uptodate */
+  if (st.st_mtime < path_st.st_mtime)
+    {
+      GTK_NOTE (ICONTHEME, 
+               g_print ("cache outdated\n"));
+      goto done; 
+    }
+
+  buffer = mmap (0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+
+  if (buffer == MAP_FAILED)
+    goto done;
+
+  /* Verify version */
+  if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
+      GET_UINT16 (buffer, 2) != MINOR_VERSION)
+    {
+      munmap (buffer, st.st_size);
+      GTK_NOTE (ICONTHEME, 
+               g_print ("wrong cache version\n"));
+      goto done;
+    }
+  
+  GTK_NOTE (ICONTHEME, 
+           g_print ("found cache for %s\n", path));
+
+  cache = g_new0 (GtkIconCache, 1);
+  cache->ref_count = 1;
+  cache->buffer = buffer;
+  cache->size = st.st_size;
+  
+ done:
+  close (fd);
+
+  return cache;
+}
+
+static int
+get_directory_index (GtkIconCache *cache,
+                    const gchar *directory)
+{
+  guint32 dir_list_offset;
+  int n_dirs;
+  int i;
+  
+  dir_list_offset = GET_UINT32 (cache->buffer, 8);
+
+  n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
+
+  for (i = 0; i < n_dirs; i++)
+    {
+      guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
+      gchar *name = cache->buffer + name_offset;
+      if (strcmp (name, directory) == 0)
+       return i;
+    }
+  
+  return -1;
+}
+
+gboolean
+_gtk_icon_cache_has_directory (GtkIconCache *cache,
+                              const gchar *directory)
+{
+  return get_directory_index (cache, directory) != -1;
+}
+
+static guint
+icon_name_hash (gconstpointer key)
+{
+  const char *p = key;
+  guint h = *p;
+
+  if (h)
+    for (p += 1; *p != '\0'; p++)
+      h = (h << 5) - h + *p;
+
+  return h;
+}
+
+gint
+_gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
+                               const gchar  *icon_name,
+                               const gchar  *directory)
+{
+  guint32 hash_offset;
+  guint32 n_buckets;
+  guint32 chain_offset;
+  int hash, directory_index;
+  guint32 image_list_offset, n_images;
+  gboolean found = FALSE;
+  int i;
+  
+  hash_offset = GET_UINT32 (cache->buffer, 4);
+  n_buckets = GET_UINT32 (cache->buffer, hash_offset);
+
+  hash = icon_name_hash (icon_name) % n_buckets;
+
+  chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
+  while (chain_offset != 0xffffffff)
+    {
+      guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
+      gchar *name = cache->buffer + name_offset;
+
+      if (strcmp (name, icon_name) == 0)
+       {
+         found = TRUE;
+         break;
+       }
+         
+      chain_offset = GET_UINT32 (cache->buffer, chain_offset);
+    }
+
+  if (!found)
+    return 0;
+
+  /* We've found an icon list, now check if we have the right icon in it */
+  directory_index = get_directory_index (cache, directory);
+  image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
+  n_images = GET_UINT32 (cache->buffer, image_list_offset);
+  
+  for (i = 0; i < n_images; i++)
+    {
+      if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
+         directory_index)
+       return GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i + 2);
+    }
+  
+  return 0;
+}
+
+void
+_gtk_icon_cache_add_icons (GtkIconCache *cache,
+                          const gchar  *directory,
+                          GHashTable   *hash_table)
+{
+  int directory_index;
+  guint32 hash_offset, n_buckets;
+  guint32 chain_offset;
+  guint32 image_list_offset, n_images;
+  int i, j;
+  
+  directory_index = get_directory_index (cache, directory);
+
+  if (directory_index == -1)
+    return;
+  
+  hash_offset = GET_UINT32 (cache->buffer, 4);
+  n_buckets = GET_UINT32 (cache->buffer, hash_offset);
+  
+  for (i = 0; i < n_buckets; i++)
+    {
+      chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
+      while (chain_offset != 0xffffffff)
+       {
+         guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
+         gchar *name = cache->buffer + name_offset;
+         
+         image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
+         n_images = GET_UINT32 (cache->buffer, image_list_offset);
+  
+         for (j = 0; j < n_images; j++)
+           {
+             if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
+                 directory_index)
+               g_hash_table_insert (hash_table, name, NULL);
+           }
+
+         chain_offset = GET_UINT32 (cache->buffer, chain_offset);
+       }
+    }
+  
+}
diff --git a/gtk/gtkiconcache.h b/gtk/gtkiconcache.h
new file mode 100644 (file)
index 0000000..ee9d48a
--- /dev/null
@@ -0,0 +1,41 @@
+/* gtkiconcache.h
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GTK_ICON_CACHE_H__
+#define __GTK_ICON_CACHE_H__
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+typedef struct _GtkIconCache GtkIconCache;
+
+GtkIconCache *_gtk_icon_cache_new_for_path   (const gchar  *path);
+gboolean      _gtk_icon_cache_has_directory  (GtkIconCache *cache,
+                                             const gchar  *directory);
+void         _gtk_icon_cache_add_icons      (GtkIconCache *cache,
+                                             const gchar  *directory,
+                                             GHashTable   *hash_table);
+
+gint          _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
+                                             const gchar  *icon_name,
+                                             const gchar  *directory);
+
+GtkIconCache *_gtk_icon_cache_ref            (GtkIconCache *cache);
+void          _gtk_icon_cache_unref          (GtkIconCache *cache);
+
+
+#endif /* __GTK_ICON_CACHE_H__ */
index 6ac490e32df4756c618f06718af35e85859ba1d8..b2053d620c9b63132deed6876b6d62e2301ee698 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "gtkicontheme.h"
 #include "gtkiconthemeparser.h"
+#include "gtkiconcache.h"
 #include "gtkintl.h"
 #include "gtksettings.h"
 #include "gtkprivate.h"
@@ -61,7 +62,8 @@ typedef enum
   ICON_SUFFIX_NONE = 0,
   ICON_SUFFIX_XPM = 1 << 0,
   ICON_SUFFIX_SVG = 1 << 1,
-  ICON_SUFFIX_PNG = 1 << 2
+  ICON_SUFFIX_PNG = 1 << 2,
+  HAS_ICON_FILE = 1 << 3
 } IconSuffix;
 
 
@@ -81,7 +83,8 @@ struct _GtkIconThemePrivate
    */
   GList *themes;
   GHashTable *unthemed_icons;
-
+  GList *unthemed_icons_caches;
+  
   /* Note: The keys of this hashtable are owned by the
    * themedir and unthemed hashtables.
    */
@@ -132,6 +135,11 @@ typedef struct
   char *comment;
   char *example;
 
+  /* Icon caches, per theme directory, key is NULL if
+   * no cache exists for that directory
+   */
+  GHashTable *icon_caches;
+  
   /* In search order */
   GList *dirs;
 } IconTheme;
@@ -158,6 +166,9 @@ typedef struct
   int threshold;
 
   char *dir;
+  char *subdir;
+  
+  GtkIconCache *cache;
   
   GHashTable *icons;
   GHashTable *icon_data;
@@ -204,6 +215,14 @@ static void         do_theme_change   (GtkIconTheme     *icon_theme);
 static void  blow_themes               (GtkIconTheme    *icon_themes);
 
 static void  icon_data_free            (GtkIconData          *icon_data);
+static void load_icon_data (IconThemeDir *dir,
+                           const char   *path,
+                           const char   *name);
+
+static IconSuffix theme_dir_get_icon_suffix (IconThemeDir *dir,
+                                            const gchar  *icon_name,
+                                            gboolean     *has_icon_file);
+
 
 static GtkIconInfo *icon_info_new             (void);
 static GtkIconInfo *icon_info_new_builtin     (BuiltinIcon *icon);
@@ -499,8 +518,12 @@ pixbuf_supports_svg ()
 {
   GSList *formats = gdk_pixbuf_get_formats ();
   GSList *tmp_list;
-  gboolean found_svg = FALSE;
+  static gboolean found_svg = FALSE;
+  static gboolean value_known = FALSE;
 
+  if (value_known)
+    return found_svg;
+  
   for (tmp_list = formats; tmp_list && !found_svg; tmp_list = tmp_list->next)
     {
       gchar **mime_types = gdk_pixbuf_format_get_mime_types (tmp_list->data);
@@ -516,7 +539,8 @@ pixbuf_supports_svg ()
     }
 
   g_slist_free (formats);
-    
+  value_known = TRUE;
+  
   return found_svg;
 }
 
@@ -553,7 +577,8 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme)
   priv->themes_valid = FALSE;
   priv->themes = NULL;
   priv->unthemed_icons = NULL;
-
+  priv->unthemed_icons_caches = NULL;
+  
   priv->pixbuf_supports_svg = pixbuf_supports_svg ();
 }
 
@@ -569,6 +594,8 @@ do_theme_change (GtkIconTheme *icon_theme)
 {
   GtkIconThemePrivate *priv = icon_theme->priv;
   
+  GTK_NOTE (ICONTHEME, 
+           g_print ("change to icon theme \"%s\"\n", priv->current_theme));
   blow_themes (icon_theme);
   g_signal_emit (icon_theme, signal_changed, 0);
   
@@ -579,6 +606,16 @@ do_theme_change (GtkIconTheme *icon_theme)
     }
 }
 
+static void 
+free_cache (gpointer data, 
+           gpointer user_data)
+{
+  GtkIconCache *cache = (GtkIconCache *)data;
+
+  if (cache)
+    _gtk_icon_cache_unref (cache);
+}
+
 static void
 blow_themes (GtkIconTheme *icon_theme)
 {
@@ -592,9 +629,13 @@ blow_themes (GtkIconTheme *icon_theme)
       g_list_foreach (priv->dir_mtimes, (GFunc)free_dir_mtime, NULL);
       g_list_free (priv->dir_mtimes);
       g_hash_table_destroy (priv->unthemed_icons);
+      if (priv->unthemed_icons_caches)
+       g_list_foreach (priv->unthemed_icons_caches, free_cache, NULL);
+      g_list_free (priv->unthemed_icons_caches);
     }
   priv->themes = NULL;
   priv->unthemed_icons = NULL;
+  priv->unthemed_icons_caches = NULL;
   priv->dir_mtimes = NULL;
   priv->all_icons = NULL;
   priv->themes_valid = FALSE;
@@ -830,7 +871,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name)
   struct stat stat_buf;
   
   priv = icon_theme->priv;
-  
+
   for (l = priv->themes; l != NULL; l = l->next)
     {
       theme = l->data;
@@ -910,6 +951,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name)
   
   dirs = g_strsplit (directories, ",", 0);
 
+  theme->icon_caches = NULL;
   theme->dirs = NULL;
   for (i = 0; dirs[i] != NULL; i++)
       theme_subdir_load (icon_theme, theme, theme_file, dirs[i]);
@@ -973,13 +1015,25 @@ load_themes (GtkIconTheme *icon_theme)
   /* Always look in the "default" icon theme */
   insert_theme (icon_theme, DEFAULT_THEME_NAME);
   priv->themes = g_list_reverse (priv->themes);
-  
+
+
   priv->unthemed_icons = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                g_free, (GDestroyNotify)free_unthemed_icon);
 
   for (base = 0; base < icon_theme->priv->search_path_len; base++)
     {
+      GtkIconCache *cache;
       dir = icon_theme->priv->search_path[base];
+
+      cache = _gtk_icon_cache_new_for_path (dir);
+
+      if (cache != NULL)
+       {
+         priv->unthemed_icons_caches = g_list_prepend (priv->unthemed_icons_caches, cache);
+
+         continue;
+       }
+          
       gdir = g_dir_open (dir, 0, NULL);
 
       if (gdir == NULL)
@@ -1036,7 +1090,7 @@ load_themes (GtkIconTheme *icon_theme)
                    unthemed_icon->svg_filename = abs_file;
                  else
                    unthemed_icon->no_svg_filename = abs_file;
-                 
+
                  g_hash_table_insert (priv->unthemed_icons,
                                       base_name,
                                       unthemed_icon);
@@ -1357,8 +1411,8 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme,
       for (d = theme->dirs; d; d = d->next)
        {
          IconThemeDir *dir = d->data;
-         
-         suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+
+         suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL);      
          if (suffix != ICON_SUFFIX_NONE)
            {
              if (suffix == ICON_SUFFIX_SVG)
@@ -1566,16 +1620,25 @@ theme_destroy (IconTheme *theme)
 
   g_list_foreach (theme->dirs, (GFunc)theme_dir_destroy, NULL);
   g_list_free (theme->dirs);
+  
+  if (theme->icon_caches)
+    g_hash_table_destroy (theme->icon_caches);
+
   g_free (theme);
 }
 
 static void
 theme_dir_destroy (IconThemeDir *dir)
 {
-  g_hash_table_destroy (dir->icons);
+  if (dir->cache)
+      _gtk_icon_cache_unref (dir->cache);
+  else
+    g_hash_table_destroy (dir->icons);
+  
   if (dir->icon_data)
     g_hash_table_destroy (dir->icon_data);
   g_free (dir->dir);
+  g_free (dir->subdir);
   g_free (dir);
 }
 
@@ -1663,6 +1726,31 @@ best_suffix (IconSuffix suffix,
     return ICON_SUFFIX_NONE;
 }
 
+
+static IconSuffix
+theme_dir_get_icon_suffix (IconThemeDir *dir,
+                          const gchar  *icon_name,
+                          gboolean     *has_icon_file)
+{
+  IconSuffix suffix;
+
+  if (dir->cache)
+    {
+      suffix = (IconSuffix)_gtk_icon_cache_get_icon_flags (dir->cache,
+                                                          icon_name,
+                                                          dir->subdir);
+
+      if (has_icon_file)
+       {
+         *has_icon_file = suffix & HAS_ICON_FILE;
+       }
+    }
+  else
+      suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+
+  return suffix;
+}
+
 static GtkIconInfo *
 theme_lookup_icon (IconTheme          *theme,
                   const char         *icon_name,
@@ -1700,7 +1788,7 @@ theme_lookup_icon (IconTheme          *theme,
     {
       dir = l->data;
 
-      suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+      suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL);
       
       if (suffix != ICON_SUFFIX_NONE &&
          (allow_svg || suffix != ICON_SUFFIX_SVG))
@@ -1744,8 +1832,9 @@ theme_lookup_icon (IconTheme          *theme,
   if (min_dir)
     {
       GtkIconInfo *icon_info = icon_info_new ();
+      gboolean has_icon_file;
       
-      suffix = GPOINTER_TO_UINT (g_hash_table_lookup (min_dir->icons, icon_name));
+      suffix = theme_dir_get_icon_suffix (min_dir, icon_name, &has_icon_file);
       suffix = best_suffix (suffix, allow_svg);
       g_assert (suffix != ICON_SUFFIX_NONE);
       
@@ -1753,6 +1842,24 @@ theme_lookup_icon (IconTheme          *theme,
       icon_info->filename = g_build_filename (min_dir->dir, file, NULL);
       g_free (file);
 
+      if (min_dir->cache && has_icon_file)
+       {
+         gchar *icon_file_name, *icon_file_path;
+
+         icon_file_name = g_strconcat (icon_name, ".icon", NULL);
+         icon_file_path = g_build_filename (min_dir->dir, icon_file_name, NULL);
+
+         if (g_file_test (icon_file_path, G_FILE_TEST_IS_REGULAR))
+           {
+             if (min_dir->icon_data == NULL)
+               min_dir->icon_data = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                           g_free, (GDestroyNotify)icon_data_free);
+             load_icon_data (min_dir, icon_file_path, icon_file_name);
+           }
+         g_free (icon_file_name);
+         g_free (icon_file_path);
+       }
+      
       if (min_dir->icon_data != NULL)
        icon_info->data = g_hash_table_lookup (min_dir->icon_data, icon_name);
 
@@ -1779,10 +1886,21 @@ theme_list_icons (IconTheme *theme, GHashTable *icons,
 
       if (context == dir->context ||
          context == 0)
-       g_hash_table_foreach (dir->icons,
-                             add_key_to_hash,
-                             icons);
-
+       {
+         if (dir->cache)
+           {
+             _gtk_icon_cache_add_icons (dir->cache,
+                                        dir->subdir,
+                                        icons);
+                                        
+           }
+         else
+           {
+             g_hash_table_foreach (dir->icons,
+                                   add_key_to_hash,
+                                   icons);
+           }
+       }
       l = l->next;
     }
 }
@@ -1889,7 +2007,9 @@ scan_directory (GtkIconThemePrivate *icon_theme,
   char *base_name, *dot;
   char *path;
   IconSuffix suffix, hash_suffix;
-  
+
+  GTK_NOTE (ICONTHEME, 
+           g_print ("scanning directory %s\n", full_dir));
   dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal,
                                      g_free, NULL);
   
@@ -1998,11 +2118,35 @@ theme_subdir_load (GtkIconTheme *icon_theme,
 
   for (base = 0; base < icon_theme->priv->search_path_len; base++)
     {
+      GtkIconCache *cache;
+      gchar *theme_path;
+
       full_dir = g_build_filename (icon_theme->priv->search_path[base],
                                   theme->name,
                                   subdir,
                                   NULL);
-      if (g_file_test (full_dir, G_FILE_TEST_IS_DIR))
+
+      /* First, see if we have a cache for the directory */
+      if (!theme->icon_caches)
+       theme->icon_caches = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                   g_free, (GDestroyNotify)free_cache);
+                                                  
+      theme_path = g_build_filename (icon_theme->priv->search_path[base],
+                                    theme->name,
+                                    NULL);
+         
+      if (!g_hash_table_lookup_extended (theme->icon_caches, theme_path, 
+                                        NULL, (gpointer)&cache))
+       {
+         /* This will return NULL if the cache doesn't exist or is outdated */
+         cache = _gtk_icon_cache_new_for_path (theme_path);
+
+         g_hash_table_insert (theme->icon_caches, g_strdup (theme_path), cache);
+       }
+
+      g_free (theme_path);
+
+      if (cache != NULL || g_file_test (full_dir, G_FILE_TEST_IS_DIR))
        {
          dir = g_new (IconThemeDir, 1);
          dir->type = type;
@@ -2013,8 +2157,14 @@ theme_subdir_load (GtkIconTheme *icon_theme,
          dir->threshold = threshold;
          dir->dir = full_dir;
          dir->icon_data = NULL;
-         
-         scan_directory (icon_theme->priv, dir, full_dir);
+         dir->subdir = g_strdup (subdir);
+         if (cache != NULL)
+           dir->cache = _gtk_icon_cache_ref (cache);
+         else
+           {
+             dir->cache = NULL;
+             scan_directory (icon_theme->priv, dir, full_dir);
+           }
 
          theme->dirs = g_list_prepend (theme->dirs, dir);
        }
index a078645912506caeb4e74d793a477542c88d358c..40acba8fd6c33951f4491f1ae0046e3a6b3739ff 100644 (file)
@@ -158,7 +158,8 @@ static const GDebugKey gtk_debug_keys[] = {
   {"keybindings", GTK_DEBUG_KEYBINDINGS},
   {"multihead", GTK_DEBUG_MULTIHEAD},
   {"modules", GTK_DEBUG_MODULES},
-  {"geometry", GTK_DEBUG_GEOMETRY}
+  {"geometry", GTK_DEBUG_GEOMETRY},
+  {"icontheme", GTK_DEBUG_ICONTHEME}
 };
 
 static const guint gtk_ndebug_keys = sizeof (gtk_debug_keys) / sizeof (GDebugKey);
diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c
new file mode 100644 (file)
index 0000000..e6957cf
--- /dev/null
@@ -0,0 +1,628 @@
+/* updateiconcache.c
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <utime.h>
+
+#include <glib.h>
+
+#define CACHE_NAME "icon-theme.cache"
+
+#define HAS_SUFFIX_XPM (1 << 0)
+#define HAS_SUFFIX_SVG (1 << 1)
+#define HAS_SUFFIX_PNG (1 << 2)
+#define HAS_ICON_FILE  (1 << 3)
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 0
+#define HASH_OFFSET 12
+
+#define ALIGN_VALUE(this, boundary) \
+  (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
+
+gboolean
+is_cache_up_to_date (const gchar *path)
+{
+  struct stat path_stat, cache_stat;
+  gchar *cache_path;
+  int retval;
+  
+  retval = stat (path, &path_stat);
+
+  if (retval < 0)
+    {
+      /* We can't stat the path,
+       * assume we have a updated cache */
+      return TRUE;
+    }
+
+  cache_path = g_build_filename (path, CACHE_NAME, NULL);
+  retval = stat (cache_path, &cache_stat);
+  g_free (cache_path);
+  
+  if (retval < 0 && errno == ENOENT)
+    {
+      /* Cache file not found */
+      return FALSE;
+    }
+
+  /* Check mtime */
+  return cache_stat.st_mtime <= path_stat.st_mtime;
+}
+
+typedef struct
+{
+  int flags;
+  int dir_index;
+} Image;
+
+static gboolean
+foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
+{
+  GHashTable *files = user_data;
+  GList *list;
+  gboolean free_key = FALSE;;
+  
+  list = g_hash_table_lookup (files, key);
+  if (list)
+    free_key = TRUE;
+  
+  list = g_list_prepend (list, value);
+  g_hash_table_insert (files, key, list);
+  
+  if (free_key)
+    g_free (key);
+  
+  return TRUE;
+}
+
+GList *
+scan_directory (const gchar *base_path, 
+               const gchar *subdir, 
+               GHashTable  *files, 
+               GList       *directories,
+               gint         depth)
+{
+  GHashTable *dir_hash;
+  GDir *dir;
+  const gchar *name;
+  gchar *dir_path;
+  gboolean dir_added = FALSE;
+  guint dir_index = 0xffff;
+  
+  dir_path = g_build_filename (base_path, subdir, NULL);
+
+  /* FIXME: Use the gerror */
+  dir = g_dir_open (dir_path, 0, NULL);
+  
+  if (!dir)
+    return directories;
+  
+  dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+  while ((name = g_dir_read_name (dir)))
+    {
+      gchar *path;
+      gboolean retval;
+      int flags = 0;
+      Image *image;
+      gchar *basename, *dot;
+
+      path = g_build_filename (dir_path, name, NULL);
+      retval = g_file_test (path, G_FILE_TEST_IS_DIR);
+      if (retval)
+       {
+         gchar *subsubdir;
+
+         if (subdir)
+           subsubdir = g_build_filename (subdir, name, NULL);
+         else
+           subsubdir = g_strdup (name);
+         directories = scan_directory (base_path, subsubdir, files, 
+                                       directories, depth + 1);
+         g_free (subsubdir);
+
+         continue;
+       }
+
+      retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
+      g_free (path);
+      
+      if (retval)
+       {
+         if (g_str_has_suffix (name, ".png"))
+           flags |= HAS_SUFFIX_PNG;
+         else if (g_str_has_suffix (name, ".svg"))
+           flags |= HAS_SUFFIX_SVG;
+         else if (g_str_has_suffix (name, ".xpm"))
+           flags |= HAS_SUFFIX_XPM;
+         else if (g_str_has_suffix (name, ".icon"))
+           flags |= HAS_ICON_FILE;
+         
+         if (flags == 0)
+           continue;
+         
+         basename = g_strdup (name);
+         dot = strrchr (basename, '.');
+         *dot = '\0';
+         
+         image = g_hash_table_lookup (dir_hash, basename);
+         if (image)
+           image->flags |= flags;
+         else if ((flags & HAS_ICON_FILE) != HAS_ICON_FILE)
+           {
+             if (!dir_added) 
+               {
+                 dir_added = TRUE;
+                 if (subdir)
+                   {
+                     dir_index = g_list_length (directories);
+                     directories = g_list_append (directories, g_strdup (subdir));
+                   }
+                 else
+                   dir_index = 0xffff;
+               }
+               
+             image = g_new0 (Image, 1);
+             image->flags = flags;
+             image->dir_index = dir_index;
+             
+             g_hash_table_insert (dir_hash, g_strdup (basename), image);
+           }
+
+         g_free (basename);
+       }
+    }
+
+  g_dir_close (dir);
+
+  /* Move dir into the big file hash */
+  g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
+  
+  g_hash_table_destroy (dir_hash);
+
+  return directories;
+}
+
+typedef struct _HashNode HashNode;
+
+struct _HashNode
+{
+  HashNode *next;
+  gchar *name;
+  GList *image_list;
+};
+
+static guint
+icon_name_hash (gconstpointer key)
+{
+  const char *p = key;
+  guint h = *p;
+
+  if (h)
+    for (p += 1; *p != '\0'; p++)
+      h = (h << 5) - h + *p;
+
+  return h;
+}
+
+typedef struct {
+  gint size;
+  HashNode **nodes;
+} HashContext;
+
+static gboolean
+convert_to_hash (gpointer key, gpointer value, gpointer user_data)
+{
+  HashContext *context = user_data;
+  guint hash;
+  HashNode *node;
+  
+  hash = icon_name_hash (key) % context->size;
+
+  node = g_new0 (HashNode, 1);
+  node->next = NULL;
+  node->name = key;
+  node->image_list = value;
+
+  if (context->nodes[hash] != NULL)
+    node->next = context->nodes[hash];
+
+  context->nodes[hash] = node;
+  
+  return TRUE;
+}
+
+gboolean
+write_string (FILE *cache, const gchar *n)
+{
+  gchar *s;
+  int i, l;
+  
+  l = ALIGN_VALUE (strlen (n) + 1, 4);
+  
+  s = g_malloc0 (l);
+  strcpy (s, n);
+
+  i = fwrite (s, l, 1, cache);
+
+  return i == 1;
+  
+}
+
+gboolean
+write_card16 (FILE *cache, guint16 n)
+{
+  int i;
+  gchar s[2];
+
+  *((guint16 *)s) = GUINT16_TO_BE (n);
+  
+  i = fwrite (s, 2, 1, cache);
+
+  return i == 1;
+}
+
+gboolean
+write_card32 (FILE *cache, guint32 n)
+{
+  int i;
+  gchar s[4];
+
+  *((guint32 *)s) = GUINT32_TO_BE (n);
+  
+  i = fwrite (s, 4, 1, cache);
+
+  return i == 1;
+}
+
+static gboolean
+write_header (FILE *cache, guint32 dir_list_offset)
+{
+  return (write_card16 (cache, MAJOR_VERSION) &&
+         write_card16 (cache, MINOR_VERSION) &&
+         write_card32 (cache, HASH_OFFSET) &&
+         write_card32 (cache, dir_list_offset));
+}
+
+
+guint
+get_single_node_size (HashNode *node)
+{
+  int len = 0;
+
+  /* Node pointers */
+  len += 12;
+
+  /* Name */
+  len += ALIGN_VALUE (strlen (node->name) + 1, 4);
+
+  /* Image list */
+  len += 4 + g_list_length (node->image_list) * 8;
+
+  return len;
+}
+
+guint
+get_bucket_size (HashNode *node)
+{
+  int len = 0;
+
+  while (node)
+    {
+      len += get_single_node_size (node);
+
+      node = node->next;
+    }
+
+  return len;
+}
+
+gboolean
+write_bucket (FILE *cache, HashNode *node, int *offset)
+{
+  while (node != NULL)
+    {
+      int next_offset = *offset + get_single_node_size (node);
+      int i, len;
+      GList *list;
+         
+      /* Chain offset */
+      if (node->next != NULL)
+       {
+         if (!write_card32 (cache, next_offset))
+           return FALSE;
+       }
+      else
+       {
+         if (!write_card32 (cache, 0xffffffff))
+           return FALSE;
+       }
+      
+      /* Icon name offset */
+      if (!write_card32 (cache, *offset + 12))
+       return FALSE;
+      
+      /* Image list offset */
+      if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4)))
+       return FALSE;
+      
+      /* Icon name */
+      if (!write_string (cache, node->name))
+       return FALSE;
+      
+      /* Image list */
+      len = g_list_length (node->image_list);
+      if (!write_card32 (cache, len))
+       return FALSE;
+      
+      list = node->image_list;
+      for (i = 0; i < len; i++)
+       {
+         Image *image = list->data;
+         
+         /* Directory index */
+         if (!write_card16 (cache, image->dir_index))
+           return FALSE;
+         
+         /* Flags */
+         if (!write_card16 (cache, image->flags))
+           return FALSE;
+         
+         /* Image data offset */
+         if (!write_card32 (cache, 0))
+           return FALSE;
+         
+         list = list->next;
+       }
+
+      *offset = next_offset;
+      node = node->next;
+    }
+  
+  return TRUE;
+}
+
+gboolean
+write_hash_table (FILE *cache, HashContext *context, int *new_offset)
+{
+  int offset = HASH_OFFSET;
+  int node_offset;
+  int i;
+
+  if (!(write_card32 (cache, context->size)))
+    return FALSE;
+
+  /* Size int + size * 4 */
+  node_offset = offset + 4 + context->size * 4;
+  
+  for (i = 0; i < context->size; i++)
+    {
+      if (context->nodes[i] != NULL)
+       {
+         if (!write_card32 (cache, node_offset))
+           return FALSE;
+         
+         node_offset += get_bucket_size (context->nodes[i]);
+       }
+      else
+       {
+         if (!write_card32 (cache, 0xffffffff))
+           {
+             return FALSE;
+           }
+       }
+    }
+
+  *new_offset = node_offset;
+
+  /* Now write the buckets */
+  node_offset = offset + 4 + context->size * 4;
+  
+  for (i = 0; i < context->size; i++)
+    {
+      if (!context->nodes[i])
+       continue;
+
+      if (!write_bucket (cache, context->nodes[i], &node_offset))
+       return FALSE;
+    }
+
+  return TRUE;
+}
+
+gboolean
+write_dir_index (FILE *cache, int offset, GList *directories)
+{
+  int n_dirs;
+  GList *d;
+  char *dir;
+
+  n_dirs = g_list_length (directories);
+
+  if (!write_card32 (cache, n_dirs))
+    return FALSE;
+
+  offset += 4 + n_dirs * 4;
+
+  for (d = directories; d; d = d->next)
+    {
+      dir = d->data;
+      if (!write_card32 (cache, offset))
+       return FALSE;
+      
+      offset += ALIGN_VALUE (strlen (dir) + 1, 4);
+    }
+
+  for (d = directories; d; d = d->next)
+    {
+      dir = d->data;
+
+      if (!write_string (cache, dir))
+       return FALSE;
+    }
+  
+  return TRUE;
+}
+
+gboolean
+write_file (FILE *cache, GHashTable *files, GList *directories)
+{
+  HashContext context;
+  int new_offset;
+
+  /* Convert the hash table into something looking a bit more
+   * like what we want to write to disk.
+   */
+  context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
+  context.nodes = g_new0 (HashNode *, context.size);
+  
+  g_hash_table_foreach_remove (files, convert_to_hash, &context);
+
+  /* Now write the file */
+  /* We write 0 as the directory list offset and go
+   * back and change it later */
+  if (!write_header (cache, 0))
+    {
+      g_printerr ("Failed to write header\n");
+      return FALSE;
+    }
+
+  if (!write_hash_table (cache, &context, &new_offset))
+    {
+      g_printerr ("Failed to write hash table\n");
+      return FALSE;
+    }
+
+  if (!write_dir_index (cache, new_offset, directories))
+    {
+      g_printerr ("Failed to write directory index\n");
+      return FALSE;
+    }
+  
+  rewind (cache);
+
+  if (!write_header (cache, new_offset))
+    {
+      g_printerr ("Failed to rewrite header\n");
+      return FALSE;
+    }
+    
+  return TRUE;
+}
+
+void
+build_cache (const gchar *path)
+{
+  gchar *cache_path, *tmp_cache_path;
+  GHashTable *files;
+  gboolean retval;
+  FILE *cache;
+  struct stat path_stat, cache_stat;
+  struct utimbuf utime_buf;
+  GList *directories = NULL;
+  
+  tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
+  cache = fopen (tmp_cache_path, "w");
+  
+  if (!cache)
+    {
+      g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
+      exit (1);
+    }
+
+  files = g_hash_table_new (g_str_hash, g_str_equal);
+  
+  directories = scan_directory (path, NULL, files, NULL, 0);
+
+  if (g_hash_table_size (files) == 0)
+    {
+      /* Empty table, just close and remove the file */
+
+      fclose (cache);
+      unlink (tmp_cache_path);
+      exit (0);
+    }
+    
+  /* FIXME: Handle failure */
+  retval = write_file (cache, files, directories);
+  fclose (cache);
+
+  g_list_foreach (directories, (GFunc)g_free, NULL);
+  g_list_free (directories);
+  
+  if (!retval)
+    {
+      unlink (tmp_cache_path);
+      exit (1);
+    }
+
+  cache_path = g_build_filename (path, CACHE_NAME, NULL);
+
+  if (rename (tmp_cache_path, cache_path) == -1)
+    {
+      unlink (tmp_cache_path);
+      exit (1);
+    }
+
+  /* Update time */
+  /* FIXME: What do do if an error occurs here? */
+  stat (path, &path_stat);
+  stat (cache_path, &cache_stat);
+
+  utime_buf.actime = path_stat.st_atime;
+  utime_buf.modtime = cache_stat.st_mtime;
+  utime (path, &utime_buf);
+  
+  g_printerr ("Cache file created successfully.\n");
+}
+
+static gboolean force_update = FALSE;
+
+static GOptionEntry args[] = {
+  { "force", 0, 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
+  { NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+  gchar *path;
+  GOptionContext *context;
+
+  context = g_option_context_new ("ICONPATH");
+  g_option_context_add_main_entries (context, args, NULL);
+
+  g_option_context_parse (context, &argc, &argv, NULL);
+  
+  path = argv[1];
+  
+  if (!force_update && is_cache_up_to_date (path))
+    return 0;
+
+  build_cache (path);
+  
+  return 0;
+}